#include "renderer.h"
#include <commctrl.h>
#include "d3d9renderer.h"
#include "dxgirenderer.h"

OverlayRenderer::OverlayRenderer(HINSTANCE hInstance, HMODULE hD3D9, HMODULE hD3D10, HMODULE hD3D11)
  : data(false)
  , d3d9Renderer(NULL)
{
  memset(&state, 0, sizeof state);
  state.data = &data;

  if (hD3D9)
    d3d9Renderer = new D3D9Renderer(hInstance, hD3D9, &state);
  if (hD3D10 || hD3D11)
    dxgiRenderer = new DXGIRenderer(hInstance, hD3D10, hD3D11, &state);
}

OverlayRenderer::~OverlayRenderer()
{
  delete d3d9Renderer;
  delete dxgiRenderer;
}

void OverlayRenderer::onDestroy(HWND hWnd)
{
  if (d3d9Renderer)
    d3d9Renderer->onDestroy(hWnd);
  if (dxgiRenderer)
    dxgiRenderer->onDestroy(hWnd);
}

enum {
  RESIZE_LEFT     = 0x01,
  RESIZE_TOP      = 0x02,
  RESIZE_RIGHT    = 0x04,
  RESIZE_BOTTOM   = 0x08,

  RESIZE_SCROLL   = 0x10,

  RESIZE_BORDER   = 8,
};

bool OverlayRenderer::onMouseEvent(unsigned long msg, MOUSEHOOKSTRUCTEX const* mh)
{
  SharedHeader* hdr = data.lock(100);
  if (!hdr) return false;

  bool result = false;
  if (mh->hwnd == hdr->hWnd)
  {
    POINT pt = mh->pt;
    ScreenToClient(hdr->hWnd, &pt);
    state.display = 0;

    if (state.resizing || pt.x >= hdr->left && pt.x < hdr->right && pt.y >= hdr->top && pt.y < hdr->bottom)
    {
      FrameHeader* frame = data.frame();
      ScrollHeader* scroll = data.scroll();
      ReplyHeader* reply = data.reply();
      int replyHeight = 0;
      if ((state.showReply || state.hoverReply) && reply->width && reply->height)
        replyHeight += reply->height - 2;
      if (pt.x >= hdr->left + (frame->width - scroll->barRight - scroll->width) * (hdr->right - hdr->left) / frame->width &&
          pt.x < hdr->left + (frame->width - scroll->barRight) * (hdr->right - hdr->left) / frame->width &&
          pt.y >= hdr->top + scroll->barTop * (hdr->bottom - hdr->top) / frame->height &&
          pt.y < hdr->top + (frame->height - scroll->barBottom) * (hdr->bottom - hdr->top) / frame->height)
        state.display |= DISPLAY_SCROLL;
      if (msg == WM_LBUTTONDOWN)
      {
        state.resizing = 0;
        if (pt.x >= hdr->left && pt.x < hdr->right && pt.y >= hdr->top && pt.y < hdr->bottom)
        {
          if (scroll->width && scroll->height && hdr->maxScroll >= 0)
          {
            int height = frame->height - scroll->barTop - scroll->barBottom - replyHeight;
            int content = frame->height - frame->insetTop - frame->insetBottom - replyHeight;
            int maxScroll = hdr->maxScroll + content;
            int barHeight = height * content / maxScroll;
            int barOffset = height * hdr->scrollPos / maxScroll;
            RECT rcScroll;
            rcScroll.left = frame->width - scroll->barRight - scroll->width;
            rcScroll.top = scroll->barTop + barOffset;
            rcScroll.right = frame->width - scroll->barRight;
            rcScroll.bottom = rcScroll.top + barHeight;
            rcScroll.left = hdr->left + rcScroll.left * (hdr->right - hdr->left) / frame->width;
            rcScroll.top = hdr->top + rcScroll.top * (hdr->bottom - hdr->top) / frame->height;
            rcScroll.right = hdr->left + rcScroll.right * (hdr->right - hdr->left) / frame->width;
            rcScroll.bottom = hdr->top + rcScroll.bottom * (hdr->bottom - hdr->top) / frame->height;
            if (pt.x >= rcScroll.left && pt.x < rcScroll.right &&
                pt.y >= rcScroll.top && pt.y < rcScroll.bottom)
            {
              state.resizing = RESIZE_SCROLL;
              state.scrollTop = rcScroll.top;
            }
          }
          if (state.resizing == 0)
          {
            if (pt.x < hdr->left + RESIZE_BORDER) state.resizing |= RESIZE_LEFT;
            if (pt.y < hdr->top + RESIZE_BORDER) state.resizing |= RESIZE_TOP;
            if (pt.x >= hdr->right - RESIZE_BORDER) state.resizing |= RESIZE_RIGHT;
            if (pt.y >= hdr->bottom - RESIZE_BORDER) state.resizing |= RESIZE_BOTTOM;
          }
          if (state.resizing == 0)
            state.resizing = RESIZE_LEFT | RESIZE_TOP | RESIZE_RIGHT | RESIZE_BOTTOM;
          state.resizePrev = pt;
        }
      }
      else if (msg == WM_LBUTTONUP)
        state.resizing = 0;
      else if (msg == WM_MOUSEMOVE && state.resizing)
      {
        int dx = pt.x - state.resizePrev.x;
        int dy = pt.y - state.resizePrev.y;
        if (state.resizing & RESIZE_LEFT) hdr->left += dx;
        if (state.resizing & RESIZE_TOP) hdr->top += dy;
        if (state.resizing & RESIZE_RIGHT) hdr->right += dx;
        if (state.resizing & RESIZE_BOTTOM) hdr->bottom += dy;
        if (state.resizing & RESIZE_SCROLL)
        {
          state.scrollTop += dy;
          int y = (state.scrollTop - hdr->top) * data.frame()->height /
            (hdr->bottom - hdr->top) - data.scroll()->barTop;
          int height = data.frame()->height - data.scroll()->barTop - data.scroll()->barBottom - replyHeight;
          int content = data.frame()->height - data.frame()->insetTop - data.frame()->insetBottom - replyHeight;
          int maxScroll = hdr->maxScroll + content;
          hdr->scrollPos = y * maxScroll / height;
          if (hdr->scrollPos < 0) hdr->scrollPos = 0;
          if (hdr->scrollPos > hdr->maxScroll) hdr->scrollPos = hdr->maxScroll;
        }
        state.resizePrev = pt;
      }
      else if (msg == WM_MOUSEWHEEL)
      {
        hdr->scrollPos -= GET_WHEEL_DELTA_WPARAM(mh->mouseData) * 72 / WHEEL_DELTA;
        if (hdr->scrollPos < 0) hdr->scrollPos = 0;
        if (hdr->scrollPos > hdr->maxScroll) hdr->scrollPos = hdr->maxScroll;
      }
      if (state.resizing & RESIZE_SCROLL)
        state.display |= DISPLAY_SCROLL;
      if (pt.y >= hdr->bottom - reply->height * (hdr->bottom - hdr->top) / frame->height)
      {
        if (msg == WM_LBUTTONDOWN)
          state.showReply = true;
        state.hoverReply = true;
      }
      else
        state.hoverReply = false;
      result = true;
    }
    else if (msg == WM_LBUTTONDOWN)
    {
      state.showReply = false;
      state.hoverReply = false;
    }
    else
      state.hoverReply = false;
  }

  data.unlock();

  return result;
}
void AddText(SharedHeader* hdr, wchar_t const* text, int len)
{
  int rlen = wcslen(hdr->replyText);
  int slen = hdr->replyCaret - hdr->replySelStart; if (slen < 0) slen = -slen;
  if (rlen + len - slen > 255) len = 255 - rlen + slen;
  if (len <= 0) len = 0;
  int s0 = (hdr->replyCaret < hdr->replySelStart ? hdr->replyCaret : hdr->replySelStart);
  memmove(hdr->replyText + s0 + len, hdr->replyText + s0 + slen, sizeof(wchar_t) * (rlen - slen - s0));
  memcpy(hdr->replyText + s0, text, sizeof(wchar_t) * len);
  hdr->replyText[rlen + len - slen] = 0;
  hdr->replyCaret = hdr->replySelStart = s0 + len;
}
bool OverlayRenderer::onKeyEvent(WPARAM wParam, LPARAM lParam)
{
  if (wParam == VK_F4 && (GetAsyncKeyState(VK_MENU) & 0x8000))
    return false;

  SharedData::Lock locker(&data, 100);
  if (!locker.locked()) return false;

  SharedHeader* hdr = data.header();
  ReplyHeader* reply = data.reply();

  int hk = wParam;
  if (GetAsyncKeyState(VK_MENU) & 0x8000)
    hk |= (HOTKEYF_ALT << 8);
  if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
    hk |= (HOTKEYF_CONTROL << 8);
  if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
    hk |= (HOTKEYF_SHIFT << 8);

  if ((lParam & 0x80000000) == 0 && hk == hdr->replyHotkey)
    state.showReply = !state.showReply;
  else if (state.showReply)
  {
    if ((lParam & 0x80000000) == 0)
    {
      switch (wParam)
      {
      case VK_ESCAPE:
        hdr->replyText[0] = 0;
        hdr->replyCaret = hdr->replySelStart = hdr->replyScroll = 0;
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        state.showReply = false;
        break;
      case VK_RETURN:
        PostMessage(hdr->hWndLauncher, WM_POSTREPLY, 0, 0);
        break;
      case VK_BACK:
        if (hdr->replyCaret == hdr->replySelStart && hdr->replyCaret > 0)
          hdr->replySelStart = hdr->replyCaret - 1;
        AddText(hdr, L"", 0);
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        break;
      case VK_DELETE:
        if (hdr->replyCaret == hdr->replySelStart && hdr->replyText[hdr->replyCaret])
          hdr->replySelStart = hdr->replyCaret + 1;
        AddText(hdr, L"", 0);
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        break;
      case VK_LEFT:
        if (hdr->replyCaret > 0)
          hdr->replyCaret--;
        if (!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
          hdr->replySelStart = hdr->replyCaret;
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        break;
      case VK_RIGHT:
        if (hdr->replyText[hdr->replyCaret])
          hdr->replyCaret++;
        if (!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
          hdr->replySelStart = hdr->replyCaret;
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        break;
      case VK_HOME:
        hdr->replyCaret = 0;
        if (!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
          hdr->replySelStart = hdr->replyCaret;
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        break;
      case VK_END:
        hdr->replyCaret = wcslen(hdr->replyText);
        if (!(GetAsyncKeyState(VK_SHIFT) & 0x8000))
          hdr->replySelStart = hdr->replyCaret;
        PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        break;
      default:
        {
          wchar_t buf[16];
          BYTE state[256];
          GetKeyboardState(state);
          int num = ToUnicode(wParam, (lParam >> 16), state, buf, 16, 0);
          for (int i = 0; i < num; i++)
            if (iswprint(buf[i]))
              AddText(hdr, buf + i, 1);
          if (num)
            PostMessage(hdr->hWndLauncher, WM_UPDATEREPLY, 0, 0);
        }
      }
    }
  }
  else
    return false;

  return true;
}
